<template>
    <div class="container">
        <div class="sidebar">
            <div class="sidebar-menu" draggable="true">开始节点</div>
            <div class="sidebar-menu" @click="onExport">导出</div>
        </div>
        <div
            ref="graphWrapRef"
            class="graphWrap"
            @dragenter="preventDefault"
            @dragleave="preventDefault"
            @dragover="preventDefault"
            @drop="onDrop"
        ></div>
        <div ref="minimapRef" class="minimap"></div>
    </div>

    <el-dialog
        v-model="state.visible"
        :close-on-click-modal="false"
        :show-close="false"
        draggable
        title="节点配置"
        width="800px"
    >
        <el-form ref="formRef" :model="state.ruleForm" :rules="state.rules" label-position="top">
            <el-form-item label="节点名称" prop="nodeName">
                <el-input v-model="state.ruleForm.nodeName" />
            </el-form-item>
            <el-form-item label="上级节点" prop="prevNode">
                <el-select v-model="state.ruleForm.prevNode">
                    <el-option
                        v-for="v in state.nodeList"
                        :key="v.id"
                        :label="v.label"
                        :value="v.id"
                    />
                </el-select>
            </el-form-item>
            <el-form-item label="条件分支" prop="condition">
                <el-input v-model="state.ruleForm.condition" />
            </el-form-item>
            <el-form-item label="驳回节点" prop="backNode">
                <el-select v-model="state.ruleForm.backNode">
                    <el-option
                        v-for="v in state.nodeList"
                        :key="v.id"
                        :label="v.label"
                        :value="v.id"
                    />
                </el-select>
            </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
            <el-button @click="onCancel" type="primary">取消</el-button>
            <el-button @click="onSubmit" type="primary">确定</el-button>
        </div>
    </el-dialog>
</template>

<script lang="ts" setup>
import { onMounted, reactive, ref, useTemplateRef, nextTick } from 'vue';
import { Graph, Node } from '@antv/x6';
import { Selection } from '@antv/x6-plugin-selection';
import { MiniMap } from '@antv/x6-plugin-minimap';
import { Snapline } from '@antv/x6-plugin-snapline';
import { History } from '@antv/x6-plugin-history';
import { Clipboard } from '@antv/x6-plugin-clipboard';
import { Export } from '@antv/x6-plugin-export';
import { FormRules } from 'element-plus';
import { debounce } from 'lodash-es';
import { useResizeObserver } from '@vueuse/core';

interface NodeItem {
    nodeName: string;
    prevNode: string;
    condition: string;
    backNode: string;
}

const graph = ref<Graph>();
const graphWrapRef = useTemplateRef('graphWrapRef');
const minimapRef = useTemplateRef('minimapRef');

Graph.registerNode('custom-node', {
    inherit: 'rect',
    width: 100,
    height: 40,
    attrs: {
        body: {
            stroke: '#dddddd',
            rx: 6
        }
    }
});
Graph.registerEdge('dag-edge', {
    connector: {
        name: 'rounded'
    },
    attrs: {
        line: {
            stroke: '#dddddd'
        }
    },
    router: {
        name: 'manhattan',
        args: {
            offset: 12
        }
    }
});

const state = reactive<{
    visible: boolean;
    nodeList: Record<string, any>[];
    edgeList: Record<string, any>[];
    currentNode: Record<string, any>;
    ruleForm: NodeItem;
    rules: FormRules;
}>({
    visible: false,
    nodeList: [
        {
            id: 'node1',
            label: 'node1',
            shape: 'custom-node',
            width: 100,
            height: 40,
            x: 100,
            y: 100
        },
        {
            id: 'node2',
            label: 'node2',
            shape: 'custom-node',
            width: 100,
            height: 40,
            x: 300,
            y: 100
        },
        {
            id: 'node3',
            label: 'node3',
            shape: 'custom-node',
            width: 100,
            height: 40,
            x: 500,
            y: 100
        },
        {
            id: 'node4',
            label: 'node4',
            shape: 'custom-node',
            width: 100,
            height: 40,
            x: 700,
            y: 100
        }
    ],
    edgeList: [
        { shape: 'dag-edge', source: 'node1', target: 'node2' },
        { shape: 'dag-edge', source: 'node2', target: 'node3' },
        { shape: 'dag-edge', source: 'node3', target: 'node4' }
    ],
    currentNode: {},
    ruleForm: {
        nodeName: '',
        prevNode: '',
        condition: '',
        backNode: ''
    },
    rules: {
        nodeName: [
            { required: true, trigger: 'blur', message: '请输入节点名称' }
        ],
        prevNode: [
            { required: true, trigger: 'change', message: '请选择上级节点' }
        ]
    }
});

onMounted(() => {
    initGraph();
    nextTick(() => {
        const data = {
            nodes: state.nodeList,
            edges: state.edgeList
        };
        graph.value?.fromJSON(data);
        addNode();
        graph.value?.centerContent();

        const nodes = graph.value?.getNodes();
        nodes?.forEach((node) => {
            node.attr('body/stroke', '#ff0000');
        });

        const edges = graph.value?.getEdges();
        edges?.forEach((edge) => {
            edge.attr('line/stroke', '#ff0000');
        });
    });
});

function initGraph() {
    graph.value = new Graph({
        container: graphWrapRef.value as HTMLDivElement,
        grid: {
            visible: true,
            type: 'mesh'
        },
        panning: true,
        mousewheel: {
            enabled: true,
            modifiers: ['ctrl', 'meta']
        },
        interacting: {
            edgeLabelMovable: false,
            nodeMovable: true,
            magnetConnectable: true
        }
    });

    graph.value.use(
        new Selection({
            enabled: true,
            multiple: true,
            movable: true,
            rubberband: true,
            showNodeSelectionBox: true,
            modifiers: ['alt', 'meta']
        })
    );

    graph.value.use(
        new Snapline({
            enabled: true
        })
    );

    graph.value.use(
        new Clipboard({
            enabled: true
        })
    );

    graph.value.use(
        new History({
            enabled: true,
            ignoreChange: true
        })
    );

    graph.value.use(
        new MiniMap({
            container: minimapRef.value as HTMLDivElement,
            width: 200,
            height: 150
        })
    );

    graph.value.use(new Export());

    const resize = debounce(() => {
        if (graphWrapRef.value) {
            const w = graphWrapRef.value?.offsetWidth;
            const h = graphWrapRef.value?.offsetHeight;
            graph.value?.resize(w, h);
        }
    }, 200);
    useResizeObserver(graphWrapRef, resize);
}

function addNode() {
    state.nodeList.push({
        id: 'node5',
        label: 'node5',
        shape: 'custom-node',
        width: 100,
        height: 40,
        x: 900,
        y: 100
    });
    graph.value?.addNode({
        id: 'node5',
        label: 'node5',
        shape: 'custom-node',
        width: 100,
        height: 40,
        x: 900,
        y: 100
    });

    graph.value?.addEdge({
        shape: 'dag-edge',
        source: 'node4',
        target: 'node5'
    });
}

function preventDefault(e: MouseEvent) {
    e.stopPropagation();
    e.preventDefault();
}

function onExport() {
    graph.value?.exportPNG('export.png');
}

function onDrop(e: MouseEvent) {
    const point = graph.value?.clientToLocal(e.clientX, e.clientY);
    const label = 'node' + state.nodeList.length + 1;

    state.currentNode = {
        id: label,
        label: label,
        x: point?.x,
        y: point?.y,
        width: 100,
        height: 40
    };
    state.visible = true;
}

function onSubmit() {
    console.log(state.currentNode, '====submit');

    state.currentNode.label = state.ruleForm.nodeName;
    state.nodeList.push(state.currentNode);
    graph.value?.addNode(state.currentNode);
    graph.value?.addEdge({
        shape: 'dag-edge',
        source: state.ruleForm.prevNode,
        target: state.currentNode.id,
        label: '审批'
    });

    if (state.ruleForm.backNode) {
        graph.value?.addEdge({
            shape: 'dag-edge',
            source: state.currentNode.id,
            target: state.ruleForm.backNode,
            label: '驳回'
        });
    }
    state.visible = false;
}

function onCancel() {
    state.visible = false;
}
</script>

<style scoped lang="scss">
.container {
    position: relative;
    width: 1000px;
    height: 600px;
    border: 1px solid #ccc;
    .sidebar {
        width: 150px;
        height: 300px;
        padding: 10px;
        position: absolute;
        top: 30px;
        left: 10px;
        z-index: 9;
        border: 1px solid #ccc;
        background: #fff;
        .sidebar-menu {
            margin-bottom: 15px;
            padding: 5px 10px;
            border: 1px solid #dddddd;
            border-radius: 4px;
            cursor: move;
            &:hover {
                background-color: #f3f3f3;
            }
        }
    }
    .graphWrap {
        width: 1000px;
        height: 600px;
    }
}
.dialog-footer {
    display: block;
    text-align: center;
    padding-top: 15px;
    border-top: 1px solid #dfdfdf;
}
</style>